package com.dbflow5.provider;

import com.dbflow5.adapter.ModelAdapter;
import com.dbflow5.config.FlowLog;
import com.dbflow5.config.FlowManager;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.FlowCursor;
import com.dbflow5.query.OperatorGroup;
import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.DataAbilityRemoteException;
import ohos.app.Context;
import ohos.data.dataability.DataAbilityPredicates;
import ohos.data.rdb.ValuesBucket;
import ohos.data.resultset.ResultSet;
import ohos.utils.net.Uri;

import java.util.ArrayList;
import java.util.List;

/**
 * Description: Provides handy wrapper mechanisms for [ContentProvider]
 */
public class ContentUtils {
    /**
     * The default content URI that ohos recommends. Not necessary, however.
     */
    public static final String BASE_CONTENT_URI = "content://";

    /**
     * Constructs an Uri with the [.BASE_CONTENT_URI] and authority. Add paths to append to the Uri.
     *
     * @param authority The authority for a [ContentProvider]
     * @param paths     The list of paths to append.
     * @return A complete Uri for a [ContentProvider]
     */
    public static Uri buildUriWithAuthority(String authority, String... paths) {
        return buildUri(BASE_CONTENT_URI, authority, paths);
    }

    /**
     * Constructs an Uri with the specified baseContent uri and authority. Add paths to append to the Uri.
     *
     * @param baseContentUri The base content URI for a [ContentProvider]
     * @param authority      The authority for a [ContentProvider]
     * @param paths          The list of paths to append.
     * @return A complete Uri for a [ContentProvider]
     */
    public static Uri buildUri(String baseContentUri, String authority, String... paths) {
        Uri.Builder builder = Uri.parse(baseContentUri + authority).makeBuilder();
        for (String path : paths) {
            builder.appendDecodedPath(path);
        }
        return builder.build();
    }

    /**
     * Inserts the model into the [ContentResolver]. Uses the insertUri to resolve
     * the reference and the model to convert its data into [ContentValues]
     *
     * @param context   context
     * @param insertUri A [Uri] from the [ContentProvider] class definition.
     * @param model     The model to insert.
     * @param <TableClass> TableClass
     * @return A Uri of the inserted data.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int insert(Context context, Uri insertUri, TableClass model) throws DataAbilityRemoteException {
        return insert(DataAbilityHelper.creator(context), insertUri, model);
    }

    /**
     * Inserts the model into the [ContentResolver]. Uses the insertUri to resolve
     * the reference and the model to convert its data into [ContentValues]
     *
     * @param contentResolver The content resolver to use
     * @param insertUri       A [Uri] from the [ContentProvider] class definition.
     * @param model           The model to insert.
     * @return The Uri of the inserted data.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int insert(DataAbilityHelper contentResolver, Uri insertUri, TableClass model) throws DataAbilityRemoteException {
        ModelAdapter<TableClass> adapter = FlowManager.modelAdapter((Class<TableClass>) model.getClass());

        ValuesBucket contentValues = new ValuesBucket();
        adapter.bindToInsertValues(contentValues, model);
        int index = contentResolver.insert(insertUri, contentValues);
        if(index >= 0) {
            adapter.updateAutoIncrement(model, index);
        }
        return index;
    }

    /**
     * Inserts the list of model into the [ContentResolver]. Binds all of the models to [ContentValues]
     * and runs the [ContentResolver.bulkInsert] method. Note: if any of these use
     * autoIncrementing primary keys the ROWID will not be properly updated from this method. If you care
     * use [.insert] instead.
     *
     * @param contentResolver The content resolver to use
     * @param bulkInsertUri   The URI to bulk insert with
     * @param table           The table to insert into
     * @param models          The models to insert.
     * @param <TableClass> TableClass
     * @return The count of the rows affected by the insert.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int bulkInsert(DataAbilityHelper contentResolver, Uri bulkInsertUri, Class<TableClass> table, List<TableClass> models) throws DataAbilityRemoteException {
        List<ValuesBucket> contentValues = new ArrayList<>();
        ModelAdapter<TableClass> adapter = FlowManager.modelAdapter(table);

        if (models != null) {
            for (TableClass model : models) {
                ValuesBucket values = new ValuesBucket();
                adapter.bindToInsertValues(values, model);
                contentValues.add(values);
            }
        }
        return contentResolver.batchInsert(bulkInsertUri, (ValuesBucket[]) contentValues.toArray());
    }

    /**
     * Inserts the list of model into the [ContentResolver]. Binds all of the models to [ContentValues]
     * and runs the [ContentResolver.bulkInsert] method. Note: if any of these use
     * autoincrement primary keys the ROWID will not be properly updated from this method. If you care
     * use [.insert] instead.
     *
     * @param context   context
     * @param bulkInsertUri The URI to bulk insert with
     * @param table         The table to insert into
     * @param models        The models to insert.
     * @param <TableClass> TableClass
     * @return The count of the rows affected by the insert.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int bulkInsert(Context context, Uri bulkInsertUri, Class<TableClass> table, List<TableClass> models) throws DataAbilityRemoteException {
        return bulkInsert(DataAbilityHelper.creator(context), bulkInsertUri, table, models);
    }

    /**
     * Updates the model through the [ContentResolver]. Uses the updateUri to
     * resolve the reference and the model to convert its data in [ContentValues]
     *
     * @param context   context
     * @param updateUri A [Uri] from the [ContentProvider]
     * @param model     A model to update
     * @param <TableClass> TableClass
     * @return The number of rows updated.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int update(Context context, Uri updateUri, TableClass model) throws DataAbilityRemoteException {
        return update(DataAbilityHelper.creator(context), updateUri, model);
    }

    /**
     * Updates the model through the [ContentResolver]. Uses the updateUri to
     * resolve the reference and the model to convert its data in [ContentValues]
     *
     * @param contentResolver The content resolver to use
     * @param updateUri       A [Uri] from the [ContentProvider]
     * @param model           The model to update
     * @param <TableClass>    TableClass
     * @return The number of rows updated.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int update(DataAbilityHelper contentResolver, Uri updateUri, TableClass model) throws DataAbilityRemoteException {
        ModelAdapter<TableClass> adapter = FlowManager.modelAdapter((Class<TableClass>)model.getClass());

        ValuesBucket contentValues = new ValuesBucket();
        adapter.bindToContentValues(contentValues, model);

        String sql = adapter.getPrimaryConditionClause(model).getQuery();
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        // TODO sql->predicates
        int count = contentResolver.update(updateUri, contentValues, predicates);
        if (count == 0) {
            FlowLog.log(FlowLog.Level.W, "Updated failed of: " + model.getClass());
        }
        return count;
    }

    /**
     * Deletes the specified model through the [ContentResolver]. Uses the deleteUri
     * to resolve the reference and the model to [ModelAdapter.getPrimaryConditionClause]}
     *
     * @param context   context
     * @param deleteUri A [Uri] from the [ContentProvider]
     * @param model     The model to delete
     * @param <TableClass>    TableClass
     * @return The number of rows deleted.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int delete(Context context, Uri deleteUri, TableClass model) throws DataAbilityRemoteException {
        return delete(DataAbilityHelper.creator(context), deleteUri, model);
    }

    /**
     * Deletes the specified model through the [ContentResolver]. Uses the deleteUri
     * to resolve the reference and the model to [ModelAdapter.getPrimaryConditionClause]
     *
     * @param contentResolver The content resolver to use
     * @param deleteUri       A [Uri] from the [ContentProvider]
     * @param model           The model to delete
     * @param <TableClass>    TableClass
     * @return The number of rows deleted.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> int delete(DataAbilityHelper contentResolver, Uri deleteUri, TableClass model) throws DataAbilityRemoteException {
        ModelAdapter<TableClass> adapter = FlowManager.modelAdapter((Class<TableClass>)model.getClass());

        String sql = adapter.getPrimaryConditionClause(model).getQuery();
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        //TODO 转换sql->predicates
        int count = contentResolver.delete(deleteUri, predicates);

        // reset autoincrement to 0
        if (count > 0) {
            adapter.updateAutoIncrement(model, 0);
        } else {
            FlowLog.log(FlowLog.Level.W, "A delete on ${model.javaClass} within the ContentResolver appeared to fail.");
        }
        return count;
    }

    /**
     * Queries the [ContentResolver] with the specified query uri. It generates
     * the correct query and returns a [ohos.database.Cursor]
     *
     * @param contentResolver The content resolver to use
     * @param queryUri        The URI of the query
     * @param whereConditions The set of [Operator] to query the content provider.
     * @param orderBy         The order by clause without the ORDER BY
     * @param columns         The list of columns to query.
     * @return A [ohos.database.Cursor]
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static FlowCursor query(DataAbilityHelper contentResolver, Uri queryUri, OperatorGroup whereConditions, String orderBy, String... columns) throws DataAbilityRemoteException {
        String sql = whereConditions.getQuery();
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        predicates.orderByDesc(orderBy);
        ResultSet cursor = contentResolver.query(queryUri, columns, predicates);
        if(cursor != null) {
            return FlowCursor.from(cursor);
        }
        return null;
    }

    /**
     * Queries the [ContentResolver] with the specified queryUri. It will generate
     * the correct query and return a list of [TableClass]
     *
     * @param context         context
     * @param queryUri        The URI of the query
     * @param table           The table to get from.
     * @param whereConditions The set of [Operator] to query the content provider.
     * @param databaseWrapper databaseWrapper
     * @param orderBy         The order by clause without the ORDER BY
     * @param columns         The list of columns to query.
     * @param <TableClass>    TableClass
     * @return A list of [TableClass]
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> List<TableClass> queryList(Context context, Uri queryUri, Class<TableClass> table,
                                                          DatabaseWrapper databaseWrapper, OperatorGroup whereConditions,
                                                          String orderBy, String... columns) throws DataAbilityRemoteException {
        return queryList(DataAbilityHelper.creator(context), queryUri, table,
                databaseWrapper, whereConditions, orderBy, columns);
    }

    /**
     * Queries the [ContentResolver] with the specified queryUri. It will generate
     * the correct query and return a list of [TableClass]
     *
     * @param contentResolver The content resolver to use
     * @param queryUri        The URI of the query
     * @param table           The table to get from.
     * @param databaseWrapper databaseWrapper
     * @param whereConditions The set of [Operator] to query the content provider.
     * @param orderBy         The order by clause without the ORDER BY
     * @param columns         The list of columns to query.
     * @param <TableClass>    TableClass
     * @return A list of [TableClass]
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> List<TableClass> queryList(DataAbilityHelper contentResolver, Uri queryUri, Class<TableClass> table,
                                                          DatabaseWrapper databaseWrapper, OperatorGroup whereConditions,
                                                          String orderBy, String... columns) throws DataAbilityRemoteException {

        String sql = whereConditions.getQuery();
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        predicates.orderByDesc(orderBy);
        //TODO sql->predicates
        ResultSet cursor = contentResolver.query(queryUri, columns, predicates);
        FlowCursor flowCursor = null;
        if(cursor != null){
            flowCursor = FlowCursor.from(cursor);
        }

        return FlowManager.modelAdapter(table)
                .getListModelLoader()
                .load(flowCursor, databaseWrapper);
    }

    /**
     * Queries the [ContentResolver] with the specified queryUri. It will generate
     * the correct query and return a the first item from the list of [TableClass]
     *
     * @param context         context
     * @param queryUri        The URI of the query
     * @param table           The table to get from
     * @param databaseWrapper databaseWrapper
     * @param whereConditions The set of [Operator] to query the content provider.
     * @param orderBy         The order by clause without the ORDER BY
     * @param columns         The list of columns to query.
     * @param <TableClass>    TableClass
     * @return The first [TableClass] of the list query from the content provider.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> TableClass querySingle(Context context, Uri queryUri, Class<TableClass> table,
                                                      DatabaseWrapper databaseWrapper, OperatorGroup whereConditions,
                                                      String orderBy, String... columns) throws DataAbilityRemoteException {
        return querySingle(DataAbilityHelper.creator(context), queryUri, table,
                databaseWrapper, whereConditions, orderBy, columns);
    }

    /**
     * Queries the [ContentResolver] with the specified queryUri. It will generate
     * the correct query and return a the first item from the list of [TableClass]
     *
     * @param contentResolver The content resolver to use
     * @param queryUri        The URI of the query
     * @param table           The table to get from
     * @param databaseWrapper databaseWrapper
     * @param whereConditions The set of [Operator] to query the content provider.
     * @param orderBy         The order by clause without the ORDER BY
     * @param columns         The list of columns to query.
     * @param <TableClass>    TableClass
     * @return The first [TableClass] of the list query from the content provider.
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public static <TableClass> TableClass querySingle(DataAbilityHelper contentResolver, Uri queryUri, Class<TableClass> table,
                                                      DatabaseWrapper databaseWrapper, OperatorGroup whereConditions,
                                                      String orderBy, String... columns) throws DataAbilityRemoteException {
        List<TableClass> list = queryList(contentResolver, queryUri, table, databaseWrapper, whereConditions, orderBy, columns);
        if(list != null){
            return !list.isEmpty()? list.get(0) : null;
        }
        return null;
    }
}
